
package models.security;

import java.util.Calendar;
import java.util.Date;
import java.util.UUID;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

import models.Store;
import models.User;

import org.apache.commons.lang.StringUtils;

import play.db.jpa.JPA;

/**
 * An security token associated with a specific user.
 * 
 * Token are available for a certain amount of time. When accessed the lease is
 * renewed. When not accessed for some time the token is invalidated and removed
 * from persistent storage.
 * 
 * 
 * @author laurent@opprecht.info
 * 
 */
@Entity
public class Token {

    /**
     * Manage token
     * 
     * @author laurent@opprecht.info
     * 
     */
    public static class Manager {

        private int renewal = 0;

        public Manager(final int renewal) {
            this.renewal = renewal;
        }

        public Manager() {
            this(600);
        }

        /**
         * Create a new token for the user.
         * 
         * @param user
         * @return
         */
        public Token create(final User user) {
            Token result = new Token(user, renewal);
            Store.save(result);
            return result;
        }

        public void delete(final String id) {
            Token token = byId(id);
            delete(token);
        }

        public void delete(final Token token) {
            if (token == null) {
                return;
            }
            if (token.isEmpty()) {
                return;
            }

            Store.delete(token);
        }

        /**
         * Return the token for the specified id if it is valid. If token is
         * valid extends its lease. If token is invalid remove it from
         * persistent storage and returns empty.
         * 
         * @param id
         * @return
         */
        public Token get(final String id) {
            Token result = byId(id);
            if (result.isEmpty()) {
                return result;
            }
            if (!result.isValid()) {
                delete(result);
                return empty;
            }
            result.renewLease();
            return result;
        }

        /**
         * Search persistent storage and returns Token if found. If not found
         * returns empty.
         * 
         * @param id
         * @return
         */
        public Token byId(final String id) {
            if (StringUtils.isEmpty(id)) {
                return empty;
            }

            Token result = empty;
            result = JPA.em().find(Token.class, id);
            result = (result == null) ? empty : result;
            return result;
        }
    }

    /**
     * Empty session class.
     * 
     * @author laurent
     * 
     */
    public static class Empty extends Token {

        private Empty() {

        }

        @Override
        public String getId() {
            return null;
        }

        @Override
        public Date getCreated() {
            return null;
        }

        @Override
        public Date getValidity_start() {
            return null;
        }

        @Override
        public Date getValidity_end() {
            return null;
        }

        @Override
        public int getRenewal() {
            return 0;
        }

        @Override
        public boolean isValid() {
            return false;
        }

        @Override
        public User getUser() {
            return User.empty;
        }

        @Override
        public String toString() {
            return "Empty";
        }

        @Override
        public boolean isEmpty() {
            return true;
        }
    }

    public static Manager manager        = new Manager();
    public static Token   empty          = new Empty();

    @Id
    protected String      id             = null;

    protected Date        created        = null;
    protected Date        validity_start = null;
    protected Date        validity_end   = null;

    protected int         renewal        = 60;

    @ManyToOne(cascade = {}, fetch = FetchType.EAGER, optional = false)
    protected User        user           = null;

    protected Token(final User user, final int renewal) {
        this.user = user;
        this.renewal = renewal;
        id = UUID.randomUUID().toString();
        Date now = new Date();
        created = now;
        validity_start = now;
        validity_end = validity_start;
        renewLease();
    }

    public Token() {

    }

    public String getId() {
        return id;
    }

    /**
     * Created date
     * 
     * @return
     */
    public Date getCreated() {
        return created;
    }

    /**
     * Start of the validity period
     * 
     * @return
     */
    public Date getValidity_start() {
        return validity_start;
    }

    /**
     * End of the validity period
     * 
     * @return
     */
    public Date getValidity_end() {
        return validity_end;
    }

    /**
     * Returns the number seconds a lease should be extended.
     * 
     * @return
     */
    public int getRenewal() {
        return renewal;
    }

    /**
     * Returns true of the token is valid. That is if we are in the validity
     * period.
     * 
     * @return
     */
    public boolean isValid() {
        if (validity_start == null) {
            return false;
        }
        if (validity_end == null) {
            return false;
        }

        Date now = new Date();
        if (validity_start.equals(now)) {
            return true;
        }
        if (validity_end.equals(now)) {
            return true;
        }
        return validity_start.before(now) && now.before(validity_end);
    }

    /**
     * Returns the user this token is associated to if it is valid. If not valid
     * returns the empty user.
     * 
     * @return
     */
    public User getUser() {
        if (!isValid()) {
            return User.empty;
        }
        return user;
    }

    @Override
    public String toString() {
        return id.toString();
    }

    /**
     * Extend the lease for fixed amount of time.
     */
    public void renewLease() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(validity_start);
        calendar.add(Calendar.SECOND, renewal);
        validity_end = calendar.getTime();
    }

    /**
     * Returns true of the token is empty. Empty equals more or less null.
     * 
     * @return
     */
    public boolean isEmpty() {
        return false;
    }

}
